-- Current sync master
local l_currMaster;

-- Queue of outgoing messages
local l_messageQueue = { };

-- Queue of received messages
local l_incomingQueue = { };

-- True if we are to save incoming messages pending a user's decision
-- whether or not to process them
local l_queueIncoming = false; 

-- Time since last message sent.
local l_elapsed = 0.0;

-- True if we are on the network listening
local l_netEnable = false;

local l_updateRunning = false;

-- True if the user has decided not to receive sunc messages for this
-- raid.
local l_syncDialogGiven = false;

-- Number of seconds between messages
-- Should increase this if disconnects are too common.
local l_messageInterval = 0.2;

-- This is the player from which we are currently receiving a full sync.
-- May be different than l_currMaster if we are receiving a "directed"
-- sync via /sk sync <player>
local l_syncGiver;

-- True if the user has accepted the sync that is being transmitted to us.
-- This is automatically set to true for the /sk master; it's only an issue
-- for directed synchs from players that are not master.
local l_syncAccepted = false;

-- Stuff for receiving a full sync
local l_receivingSync = false;
local l_syncLists = { };
local l_syncKey;
local l_syncListName;
local l_syncInProgress = false;
local l_syncReserveList = { };

-- The saved Sync Key
SKG_SyncKey = nil;

local function print(msg)
	if( DEFAULT_CHAT_FRAME ) then
		DEFAULT_CHAT_FRAME:AddMessage(msg);
	end
end

local function queueMessage(msg, callFunction, recipient)
	-- if toPlayer is nil we use the normal sync channel, else we use
	-- a tell
	local msgInfo = {
						message = msg,
						callAfter = callFunction,
						toPlayer = recipient
	};

	table.insert(l_messageQueue, msgInfo);
	if (not l_updateRunning) then
		-- This starts giving us updates.
		l_elapsed = l_messageInterval; -- Make the first message go out immediately.
		l_updateRunning = true;
		SKG_DummyFrame:Show();
	end
end

function SKG_queueTell(msg, recipient, stype, noecho, callFunction)
	local msgInfo = {
						message = msg,
						callAfter = callFunction,
						toPlayer = recipient,
						sendType = stype,
						noEcho = noecho
	};

	table.insert(SKG_messageQueue, msgInfo);
	if (not l_updateRunning) then
		-- This starts giving us updates.
		l_elapsed = l_messageInterval; -- Make the first message go out immediately.
		l_updateRunning = true;
		SKG_DummyFrame:Show();
	end
end

local function newSyncKey()
	if (SKG_SyncKey) then
		SKG_SyncKey = SKG_SyncKey + 1;
	else
		SKG_SyncKey = math.random(1, 1000000000);
	end
end

-- Queue a message that will change a clients list(s).  Auto-inserts
-- the sync key as the first argument, then increments it.
local function queueSyncMessage(msg)
	newSyncKey();
	msg = "SyncMessage::"..SKG_SyncKey.."::"..msg;
	SKG_Debug("Queue Sync Message "..msg);
	queueMessage(msg);
end

local function getNextArg(netStr)
	-- Return the next argument in the arg list, and the rest of the
	-- string to be processed, if any.
	local divider = string.find(netStr, "::");

	if (divider) then
		return string.sub(netStr, 1, divider-1), string.sub(netStr, divider+2);
	end
	return netStr, nil;
end

-- Checks to see if we are in sync with the server.
-- Returns the message if so, requests a sync if not.
local function getSyncMessage(msg)
	local key, rest = getNextArg(msg);

	newSyncKey();
	SKG_Debug("Getting sync message, their key = "..key.." my key = "..tostring(SKG_SyncKey));
	if(tonumber(key) == SKG_SyncKey) then
		return rest;
	end
	-- Request a full sync.
	SKG_Debug("Requesting full sync. 113");
	queueMessage("SyncChallengeFail");
	return false;
end

local function queueIncomingMessage(player, message)
	if (l_queueIncoming) then
		table.insert(l_incomingQueue,
			{ player = player, msg = message }
		);
		if (#l_incomingQueue > 200) then
			-- Received 200 incoming messages, which means the user
			-- waited for at least 20 seconds before accepting or
			-- cancelling.  Guess what, he doesn't get any messages!
			l_incomingQueue = { };
			l_queueIncoming = false;
		end
	end
end


function SKG_Net_SendMessage(Message)
	if (IsInGuild() and SKG_Options.syncGuild) then  --/I
		SKG_Debug("SKG_Net_SendMessage IsInGuild(True)");
--		SendAddonMessage(SKG_CHANNEL_PREFIX, Message, "GUILD" );
		ChatThrottleLib:SendAddonMessage("BULK", SKG_CHANNEL_PREFIX, Message, "GUILD");
	end
	if (isInGroup() and SKG_Options.syncRaid) then
		SKG_Debug("SKG_Net_SendMessage isInGroup(True)");
--		SendAddonMessage(SKG_CHANNEL_PREFIX, Message, "RAID" );
		ChatThrottleLib:SendAddonMessage("BULK", SKG_CHANNEL_PREFIX, Message, "RAID");
	end
end

function SKG_IsPlayerInGroup(PlayerName)
	local groupType, groupIndex = (IsInRaid() and "raid" or "party"), (IsInRaid() and 40 or 4)
	if GetNumGroupMembers() > 0 then
		for x = 1, groupIndex do
			if UnitName(groupType..x) == PlayerName then
				return true
			end
		end
	end
--/S	if GetNumRaidMembers() > 0 then
--		for x = 1, 40 do
--			if UnitName("raid" .. x) == PlayerName then
--				return true
--			end
--		end
--	elseif GetNumPartyMembers() > 0 then
--		for x = 1, 4 do
--			if UnitName("party" .. x) == PlayerName then
--				return true
--			end
--		end
--		
--		if PlayerName == UnitName("player") then
--			return true
--		end
--	end
	if PlayerName == UnitName("player") then
		return true
	end
end

function SKG_Net_OnEvent(arg1, arg2)   --/P
	if string.sub(arg1, 1, 9) ~= "<<<SKG>>> " then
		return -- not a SuicideKings message
	end

	-- to get here, it's fine
	SKG_Net_MessageIn(arg2, string.sub(arg1, 20));
end

function SKG_Net_OnUpdate(dt)
	if (#l_messageQueue > 0 or #SKG_messageQueue > 0) then  --/L
	l_elapsed = l_elapsed + dt;
		if (l_elapsed >= l_messageInterval) then
			l_elapsed = 0.0;

			if (#l_messageQueue > 0) then  --/L
				-- Looks like one spot we need the whisper addon channel
				if (l_messageQueue[1].toPlayer) then
					-- SendChatMessage("<<<SKG>>> "..l_messageQueue[1].message, "WHISPER", nil, 
					-- l_messageQueue[1].toPlayer);
					if (strlen(SKG_CHANNEL_PREFIX) + strlen(l_messageQueue[1].message) > 254) then  --/D
					SKG_Debug(SKG_CHANNEL_PREFIX..l_messageQueue[1].message.." is too long");  --/D
					else
--					SendAddonMessage(SKG_CHANNEL_PREFIX, 
--									l_messageQueue[1].message, 
--									"WHISPER",
--									l_messageQueue[1].toPlayer);
						if (l_messageQueue[1].toPlayer ~= "GUILD" and l_messageQueue[1].toPlayer ~= "RAID") then
							SKG_Debug("216 - Sending Whisper "..l_messageQueue[1].toPlayer);
							ChatThrottleLib:SendAddonMessage("BULK",
												SKG_CHANNEL_PREFIX, 
												l_messageQueue[1].message, 
												"WHISPER",
												l_messageQueue[1].toPlayer);
						else
							if (IsInGuild() or isInGroup()) then 
								ChatThrottleLib:SendAddonMessage("BULK",
											SKG_CHANNEL_PREFIX, 
											l_messageQueue[1].message, 
											l_messageQueue[1].toPlayer);
							else
								SKG_Debug("Update off");
								--SKG_DummyFram:Hide();
								l_updateRunning = false;
							end
						end
					end
				else
					if (IsInGuild() or isInGroup()) then  --/I
--					if (IsInGuild()) then
						SKG_Net_SendMessage(l_messageQueue[1].message);
					else
						SKG_Debug("Update off");
						--SKG_DummyFram:Hide();
						l_updateRunning = false;
					end
				end

				if (l_messageQueue[1].callAfter) then
					l_messageQueue[1].callAfter();
				end
				table.remove(l_messageQueue, 1);
			end
			
			if(#SKG_messageQueue > 0) then   --/L
				SKG_Sticky_Whisper_toggle("send");	--/O
				SKG_tellPlayer(SKG_messageQueue[1].toPlayer,
								SKG_messageQueue[1].message,
								SKG_messageQueue[1].sendType,
								SKG_messageQueue[1].noEcho);
				if (SKG_messageQueue[1].callAfter) then
					SKG_messageQueue[1].callAfter();
				end
				table.remove(SKG_messageQueue, 1);
				SKG_Sticky_Whisper_toggle("finish");	--/O
			end  --/L
		end
	else
		-- Shut off updates to save CPU
		SKG_Debug("Update off line 213");
		SKG_DummyFrame:Hide();
		l_updateRunning = false;
	end
end

function SKG_Net_MessageIn(player, msg)
	if (UnitName("player") == player) then
		SKG_Debug("incoming message from "..player.." disregarded - "..msg);
		return -- Disregard messages from ourself.
	end
   
	if (not l_netEnable and not l_syncDialogGiven) then
		l_syncDialogGiven = true;

		-- Tell the net layer to save incoming messages and use them
		-- if the user chooses that he wants to listen for them.
		l_incomingQueue = { };
		l_queueIncoming = true;

		SKG_confirmDialog(SKG_format(SKG_MSG_NEW_RAID, player),
							SKG_Net_Enable, true);

		-- Prevents double dialog by assuming this guy is the master
		l_currMaster = player;
	end

	if (not l_netEnable) then
		queueIncomingMessage(player, msg);
		return
	end

	local command, rest = getNextArg(msg);
	
	if (command ~= "FullSync") then
		SKG_Debug("Net message: "..msg.." Command extracted: '"..command.."'");
	end
	if (command == "NewMaster") then
		local player = getNextArg(rest);

		SKG_NetInterface_NewMaster(player);
		if (player ~= l_currMaster) then
			SKG_Debug("Master changed, resetting sync machine.");
			SKG_Net_Enable(false);
			l_syncDialogGiven = false;
			l_currMaster = player;
		end
	elseif (command == "SyncChallengeFail" and SKG_AmITheMaster()) then
		SKG_Net_StartFullSync();
	elseif (command == "FullSyncBegin") then
		local syncKey = getNextArg(rest);

		if (player ~= l_currMaster) then
			-- Must be a directed sync from someone who's not the boss
			-- of us.  Make sure we want to receive this sync.
			l_syncAccepted = false;
			SKG_confirmDialog(SKG_format(SKG_FMT_ACCEPT_SYNC, player),
								SKG_Net_AcceptSync, player);
		else
			print("Receiving full sync from "..player);
			l_syncAccepted = true;
		end
          
		l_syncGiver = player;
		l_syncKey = tonumber(syncKey);
		-- Added by Geo
		local major, minor = getVersionComponents();
		SKG_TitleText:SetText(string.format(SKG_TITLE, major, minor, l_syncKey));
		l_syncLists = { };
		l_syncListName = nil;
		l_syncReserveList = { };
		l_receivingSync = true;
	elseif (command == "FullSyncList" and
				player == l_syncGiver and
				not SKG_AmITheMaster()) then
		local listName = getNextArg(rest);

		l_syncListName = listName;
		l_syncLists[listName] = { };
	elseif (command == "FullSync" and
				player == l_syncGiver and
				not SKG_AmITheMaster()) then
		local player, roll, frozen, insert_bottom;

		player, rest = getNextArg(rest);
		roll, rest = getNextArg(rest);
		class, rest = getNextArg(rest);  --/J
		frozen, rest = getNextArg(rest);
		insert_bottom, rest = getNextArg(rest);
		if (class == " ") then  --/J
			class = nil
		end
		
		if (insert_bottom == "true") then
			insert_bottom = true;
		else
			insert_bottom = false;
		end
		if (frozen == "true" or frozen == "1") then
			frozen = 1;
		else
			frozen = 0;
		end
		table.insert( l_syncLists[l_syncListName],
						{ player = player,
							class = class,  --/J
							frozen = frozen,
							insert_bottom = insert_bottom,
							roll = tonumber(roll) });
		--SKG_Debug("Received sync for player "..player.." in list "..l_syncListName);
	elseif (command == "FullSyncReserve" and
				player == l_syncGiver) then
		local player = getNextArg(rest);
      
		l_syncReserveList[player] = true;
	elseif (command == "FullSyncEnd" and
				player == l_syncGiver and
				l_receivingSync) then
		if (l_syncAccepted) then
			print(SKG_format(SKG_FMT_SYNC_RECEIVED, player));
			SKG_NetInterface_FullSync(l_syncLists, l_syncKey, l_syncReserveList);
		end
		l_receivingSync = false;
	elseif (command == "SyncMessage" and
				player == l_currMaster) then
		command = getSyncMessage(rest);

		SKG_Debug("Got sync command: "..tostring(command));
		if (command) then
			command, rest = getNextArg(command);

			if (command == "RemovePlayer") then    
				local index, list;

				list, rest = getNextArg(rest);
				index = tonumber(getNextArg(rest));
				SKG_NetInterface_RemovePlayer(list, index);
			elseif (command == "DeleteList") then
				local list = getNextArg(rest);

				SKG_NetInterface_DeleteList(list);
			elseif (command == "InsertPlayer") then
				local list, index, player, roll, frozen, insert_bottom, class;   --/J

				list, rest = getNextArg(rest);
				index, rest = getNextArg(rest);
				player, rest = getNextArg(rest);
				roll, rest = getNextArg(rest);
				class, rest = getNextArg(rest);  --/J
				frozen, rest = getNextArg(rest);
				insert_bottom = getNextArg(rest);
				if (class == " ") then  --/J
					class = nil
				end

				index = tonumber(index);
				roll = tonumber(roll);
				if (frozen == "true" or frozen == "1") then
					frozen = 1;
				else
					frozen = 0;
				end
				if (insert_bottom == "true") then
					insert_bottom = true;
				else
					insert_bottom = false;
				end
				SKG_NetInterface_InsertPlayer( 
					list, index, player,  roll, class, frozen, insert_bottom);   --/J
			elseif (command == "NewList") then
				local list = getNextArg(rest);

				SKG_NetInterface_NewList(list);
			elseif (command == "MovePlayer") then
				local list, oldIdx, newIdx, insert_bottom;

				list, rest = getNextArg(rest);
				oldIdx, rest = getNextArg(rest);
				newIdx = getNextArg(rest);

				oldIdx = tonumber(oldIdx);
				newIdx = tonumber(newIdx);

				SKG_NetInterface_MovePlayer(list, oldIdx, newIdx);
			elseif (command == "ReservePlayer") then
				local player, reserve;

				player, rest = getNextArg(rest);
				reserve = getNextArg(rest);

				if (reserve == "true") then
					reserve = true;
				else
					reserve = false;
				end
				SKG_NetInterface_ReservePlayer(player);
			elseif (command == "SuicidePlayer" and
						SKG_IsPlayerInGroup(player)) then
				local list, index;

				list, rest = getNextArg(rest);
				index = getNextArg(rest);
				index = tonumber(index);
				SKG_NetInterface_SuicidePlayer(list, index);
			elseif (command == "VerboseSuicide" and
						not SKG_IsPlayerInGroup(player)) then
				local list, index;
				local movingPlayers = { };

				list, rest = getNextArg(rest);
				while rest do
					index, rest = getNextArg(rest);
					table.insert(movingPlayers, tonumber(index));
				end
				SKG_NetInterface_VerboseSuicide(list, movingPlayers);
			elseif (command == "Undo") then
				SKG_NetInterface_Undo();
			end
		end
	end
end

function SKG_Net_IAmTheMaster(player)
	if (l_currMaster ~= player) then
		queueMessage("NewMaster::"..player);
		l_currMaster = player;
	end
end

function SKG_Net_IAmNotTheMaster(player)
	queueMessage("UnMaster::"..player);
	if (l_currMaster == player) then
		l_currMaster = nil;
	end
end

function SKG_Net_DeleteList(listName)
	queueSyncMessage("DeleteList::"..listName);
end

function SKG_Net_ChallengeForSync()
	if (SKG_AmITheMaster() and
			not l_syncInProgress) then
		queueMessage("SyncChallenge::"..SKG_SyncKey);
	end
end

function SKG_Net_StartFullSync(player, listFilter)
	if (l_syncInProgress) then
		SKG_Debug("Not syncing, in progress.");
		if (player) then
			print(SKG_MSG_SYNC_IN_PROGRESS);
		end
		return false;
	end

	local listsToSend = { };

	-- Transform the list for O(1) lookup
	if (listFilter) then
		for _, listName in ipairs(listFilter) do
			listsToSend[string.lower(listName)] = true;
		end
	else
		listsToSend = nil;
	end

	l_syncInProgress = true;
	print("Sending out full sync...");

	if (not SKG_SyncKey) then
		newSyncKey();
	end

	queueMessage("FullSyncBegin::"..SKG_SyncKey, nil, player);

	for listName, skList in pairs(SavedRollLists) do
		if (not listsToSend or
				listsToSend[string.lower(listName)]) then
			queueMessage("FullSyncList::"..listName, nil, player);
			if (SKG_Options.syncGuild and player == nil) then
				for _, rollInfo in ipairs(skList) do
					if (rollInfo.class == nil) then
						rollInfo.class = " "
					end
					local msg = "FullSync::"..rollInfo.player.."::"..rollInfo.roll.."::"..rollInfo.class.."::"..tostring(rollInfo.frozen).."::";   --/J
            
					if (rollInfo.insert_bottom) then
						msg = msg.."true";
					else
						msg = msg.."false";
					end
					queueMessage(msg, nil, "GUILD");
				end
			end
			if (SKG_Options.syncRaid and player == nil) then
				for _, rollInfo in ipairs(skList) do
					if (rollInfo.class == nil) then
						rollInfo.class = " "
					end
					local msg = "FullSync::"..rollInfo.player.."::"..rollInfo.roll.."::"..rollInfo.class.."::"..tostring(rollInfo.frozen).."::";   --/J
           
					if (rollInfo.insert_bottom) then
						msg = msg.."true";
					else
						msg = msg.."false";
					end
					queueMessage(msg, nil, "RAID");
				end
			end
			if (player ~= nil) then
				for _, rollInfo in ipairs(skList) do
					if (rollInfo.class == nil) then
						rollInfo.class = " "
					end
					local msg = "FullSync::"..rollInfo.player.."::"..rollInfo.roll.."::"..rollInfo.class.."::"..tostring(rollInfo.frozen).."::";   --/J
           
					if (rollInfo.insert_bottom) then
						msg = msg.."true";
					else
						msg = msg.."false";
					end
					queueMessage(msg, nil, player);
				end
			end
		end
	end

	for player, reserve in pairs(SKG_NetInterface_GetReservedPlayers()) do
		if (reserve) then
			queueMessage("FullSyncReserve::"..player, nil, player);
		end
	end

	queueMessage("FullSyncEnd", SKG_Net_SyncDone, player);
	return true;
end

function SKG_Net_SyncDone()
	l_syncInProgress = false;
	print("Full sync sent.");
end

function SKG_Net_RequestSync()
	queueMessage("SyncChallengeFail");
end 

function SKG_Net_InsertPlayer(listName, index, rollInfo)
	local insert_bottom = "true";

	if (not rollInfo.insert_bottom) then
		insert_bottom = "false";
	end
   
	-- Fix up for a bug that was corrupting lists from the network layer
	if (not rollInfo.frozen or rollInfo.frozen == 0) then
		rollInfo.frozen = 0;
	else
		rollInfo.frozen = 1;
	end
	
	if (rollInfo.class == nil) then
		rollInfo.class = " "
	end
	queueSyncMessage("InsertPlayer::"..listName.."::"..index.."::"..rollInfo.player.."::"..rollInfo.roll.."::"..rollInfo.class.."::"..rollInfo.frozen.."::"..insert_bottom);  --/J
end

function SKG_Net_NewList(listName)
	SKG_Debug("New list "..listName.." send");
	queueSyncMessage("NewList::"..listName);
end

function SKG_Net_MovePlayer(listName, oldIdx, newIdx)
	SKG_Debug("MovePlayer::"..listName.."::"..oldIdx.."::"..newIdx);
	queueSyncMessage("MovePlayer::"..listName.."::"..oldIdx.."::"..newIdx);
end

function SKG_Net_Reserve(player, reserve)
	if (reserve) then
		queueSyncMessage("ReservePlayer::"..player.."::true");
	else
		queueSyncMessage("ReservePlayer::"..player.."::false");
	end
end

function SKG_Net_RemovePlayer(listName, index)
	queueSyncMessage("RemovePlayer::"..listName.."::"..index);
end

function SKG_Net_Suicide(listName, suicideIndex)
	SKG_Debug("SuicidePlayer::"..listName.."::"..suicideIndex);
	queueSyncMessage("SuicidePlayer::"..listName.."::"..suicideIndex);
end

function SKG_Net_Undo()
	SKG_Debug("Undo");
	queueSyncMessage("Undo");
end

function SKG_Net_Enable(enable)
	SKG_Debug("Setting net enable to: "..tostring(enable));
	l_netEnable = enable;

	if (enable) then
		for _, msgInfo in ipairs(l_incomingQueue) do
			SKG_Net_MessageIn(msgInfo.player, msgInfo.msg);
		end
		l_incomingQueue = { };
		l_queueIncoming = false;
	end
end

function SKG_Net_IsEnabled()
	return l_netEnable;
end

function SKG_Net_NotInRaid()
	SKG_Net_Enable(false);
	l_syncDialogGiven = false;
	SKG_Debug("No longer in a raid, net layer notified.");
end

----- Don't think we need this anymore...
-- function SKG_Net_VariablesLoaded()
--    updateChannelFromSettings();
-- end

function SKG_Net_VerboseSuicide(listName, movingPlayers)
	local msg = "VerboseSuicide::"..listName;
   
	for _, playerIndex in ipairs(movingPlayers) do
		msg = msg.."::"..playerIndex;
	end
	queueSyncMessage(msg);
end 

function SKG_Net_AcceptSync(player)
	if (l_syncGiver ~= player) then
		print(SKG_format(SKG_FMT_ERROR_NOSYNC, player));
		return
	end

	if (l_receivingSync) then
		l_syncAccepted = true;
	else
		-- We waited a while, and the sync is already done, so use the data
		-- we have queued up.
		print(SKG_format(SKG_FMT_SYNC_RECEIVED, player));
		SKG_NetInterface_FullSync(l_syncLists, l_syncKey, l_syncReserveList);
	end
end